package com.brayan.melinxa;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.log4j.Logger;

import com.brayan.melinxa.logic.exceptions.ShadowerInitalisationException;
import com.brayan.melinxa.logic.shadowers.Shadower;
import com.brayan.melinxa.logic.shadowers.ShadowerObservable;
import com.brayan.melinxa.logic.shadowers.local2local.LocalDirectoryShadower;
import com.brayan.melinxa.logic.shadowers.locations.LocalDirectory;
import com.brayan.melinxa.logic.shadowers.locations.LocationPair;

/**
 * This class is basically created to embed a {@link Shadower} within it's own
 * Thread. Doubtless, you may invoke a type of {@link Shadower} yourself.
 * 
 * Melinxa choses to invoke a default Shadower, which is
 * {@link LocalDirectoryShadower}. Rationale: other Shadowers require multiple
 * paramters to be properly instantiated. E.g. local2ftp requires
 * 
 * @author Brayan Zimmerli
 * 
 */
@Singleton
public class Melinxa implements ShadowerObservable {
	@Inject
	private Logger logger;

	Shadower shadower = null;
	Thread threadDirShadower = null;

	SystemStatus systemState = null;
	List<SystemStateObservable> listOfSystemStateObservers = new ArrayList<SystemStateObservable>();

/**
	 * Used for testing or non-gui invocation. GUI applications simply invoke
	 * the this Class constructor (embedded in its own Thread) or invoke the
	 * {@link LocalDirectoryShadower} class directly.
	 * 
	 * Non-GUI applications assume a local2local shadower {@link LocalDirectoryShadower) if directory strings (source and destination) are provided. Otherwise no shadower is instantiated.
	 * 
	 * @param args
	 *            Add a source directory to observe and a destination directory
	 *            to mirror the observed data.
	 */
	public static void main(String[] args) {
		Melinxa nonGuiMelinxa = null;
		if (args.length == 2) {
			// If 2 strings are passed, Melinxa is going to invoke a Local
			// Directory Shadower.

			nonGuiMelinxa = new Melinxa();
			LocationPair localPair = new LocationPair(new LocalDirectory(args[0]),
					new LocalDirectory(args[1]));

			LocalDirectoryShadower dirShadower = null;
			try {
				dirShadower = LocalDirectoryShadower.getInstance(localPair);
			} catch (ShadowerInitalisationException sie) {
				// TODO use logger
				sie.printStackTrace();
			}
			nonGuiMelinxa.setShadower(dirShadower);
			nonGuiMelinxa.startTheShadower();
		} else
			throw new RuntimeException(
					"Please provide exactly two directory arguments. E.g. the source and the destination directory. If you do not provide exactly those two, the application will will be started in a deactivated mode");
	}

	/**
	 * This default constructor is used, if the type of Shadower is uncertain
	 * when an application starts Melinxa
	 */
	public Melinxa() {

	}

	/**
	 * Initialize or re-initalize. Initialize will add the existing shadower to
	 * its own thread.
	 */
	private void init() {
		threadDirShadower = new Thread(shadower, "MM::" + shadower.getTypeOfShadower());
	}

	/**
	 * In order for this method to return true: the shadower must have an
	 * initalized shadower. That is shadower != null and the shadower must
	 * indicate a ready state.
	 */
	public boolean isMelinxaReady() {
		if (shadower == null)
			return false;
		else {
			if (shadower.isReady())
				return true;
			else
				return false;
		}
	}

	/**
	 * Use this function to properly shart the shadowing functionality.
	 */
	public void startTheShadower() {
		threadDirShadower.start();
	}

	/**
	 * Close the Melinxa background processes, in particular the shadower.
	 * Melinxa may inform the listeners/observers of a state change, but is not
	 * responsible of performing any other actions.
	 */
	public void shutdown() {
		System.out.println("Trying to shutdown the melinxa engine.");
		notifySystemStateObservers(SystemStatus.CLOSED);

		if (getShadower() != null) {
			shadower.shutdown();
			// do not set the shadower to null to avoid inproper log handling.
		}

		if (threadDirShadower != null) {
			threadDirShadower.interrupt();
			threadDirShadower = null;
		}
	}

	/**
	 * Setting the {@link Shadower} for the Melinxa application doesn't
	 * necessarily mean the shadower is started. Although recommended, it
	 * depends on the underlaying implementation of the shadower.
	 * 
	 * @param shadower
	 *            Shadower responsible for data shadowing.
	 */
	public void setShadower(Shadower shadower) {
		this.shadower = shadower;
		init();
	}

	/**
	 * Get the shadower which is currently responsible to of shadowing data.
	 * 
	 * @return Shadower Could also be null if not yet set.
	 */
	public Shadower getShadower() {
		return shadower;
	}

	/** Handle this oportunity with caution. */
	public Thread getThread() {
		return threadDirShadower;
	}

	public void registerSystemStateObserver(SystemStateObservable observer) {
		listOfSystemStateObservers.add(observer);
	}

	public void deregisterSystemStateObserver(SystemStateObservable observer) {
		listOfSystemStateObservers.remove(observer);
	}

	private void notifySystemStateObservers(SystemStatus state) {
		for (SystemStateObservable observer : listOfSystemStateObservers) {
			observer.systemStateChangedTo(state);
		}
	}

	@Override
	public void shadowerStateChangedTo(SystemStatus state) {
		notifySystemStateObservers(state);
	}

	@Override
	public void registerAsShadowerStateObserver() {
		if (shadower != null)
			shadower.registerObserverOfStateChange(this);
	}

}
